/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifndef GLRENDERER_HH_
#define GLRENDERER_HH_

#include "carina/renderer-base.hh"
#include "carina/gl-library.hh"

#include "carina/common/exception.hh"

#ifdef _WIN32
#   define WIN32_LEAN_AND_MEAN 1
#   include <windows.h>
#endif

#include <GL/gl.h>
#undef __glext_h_
#include "GL/glext.h"

#include <map>

namespace Carina
{

struct GLIndTypeInfo
{
    GLsizei     size;
    GLenum      type;
    GLIndTypeInfo() {}
    GLIndTypeInfo(GLsizei s, GLenum t)
        :   size(s),
            type(t) {}
};

class GLInputLayout;
typedef shared_ptr<GLInputLayout> GLInputLayoutPtr;
typedef shared_ptr<const GLInputLayout> GLInputLayoutConstPtr;

class GLStateObject;
typedef shared_ptr<GLStateObject> GLStateObjectPtr;

class GLBlendStateObject;
typedef shared_ptr<GLBlendStateObject> GLBlendStateObjectPtr;

class GLDepthStencilStateObject;
typedef shared_ptr<GLDepthStencilStateObject> GLDepthStencilStateObjectPtr;

class GLFramebuffer;
typedef shared_ptr<GLFramebuffer> GLFramebufferPtr;

class GLWindowImpl;
class GLRenderer;

class GLSLShaderCompiler: public ShaderCompiler
{
    GLRenderer&                             m_Renderer;
public:
    GLSLShaderCompiler(GLRenderer& renderer);
     ~GLSLShaderCompiler();

    virtual ShaderProgramPtr compileShaderProgram(const string& filename) const;
};

class GLRenderer: public RendererBase
{
    Library         m_GLLib;
    
    typedef std::unordered_map<string, GLenum> EnumMap;
    EnumMap                         m_EnumMap;

    const WindowImpl&               m_Window;

    size_t                          m_Stride,
                                    m_Offset,
                                    m_IndicesOffset;

    GLenum                          m_IndiceType;
    size_t                          m_IndiceTypeSize;

    GLInputLayoutConstPtr           m_Layout;
    ShaderProgramPtr                m_Prog;
    GLStateObjectPtr                m_DefaultState,
                                    m_CurrentState;
    GLBlendStateObjectPtr           m_DefaultBlendState,
                                    m_CurrentBlendState;
    GLDepthStencilStateObjectPtr    m_DefaultDepthStencilState,
                                    m_CurrentDepthStencilState;

    IncludeLoaderPtr                m_IncludeLoader;

    GLFramebufferPtr                m_ReadFramebuffer,
                                    m_DrawFramebuffer;
    ShaderCompilerPtr               m_PreferredShaderCompiler;
    DataVector<ShaderCompilerPtr>   m_ShaderCompilers;
public:
    GLRenderer(const WindowImpl& window);
    virtual ~GLRenderer();

    virtual InputLayoutPtr createInputLayout(const ShaderProgramPtr& shader, const InputElementArray&);
    virtual void setInputLayout(const InputLayoutConstPtr& layout);

    virtual VideoBufferPtr createVideoBuffer(VBType type, size_t size, const void* data,  VBUsage usage);
    virtual void setVideoBuffer(const VideoBufferPtr& vb, size_t stride, size_t offset);
    virtual void setIndexBuffer(const VideoBufferPtr& vb, IndicesType itype, size_t offset);

    virtual void draw(DrawModes mode, size_t first, size_t count);
    virtual void drawIndexed(DrawModes mode, size_t first, size_t count);

    virtual ShaderCompilerPtr getPreferredShaderCompiler(const ShadingLanguagesList& shlang_list);
    virtual ShaderCompilerPtr getShaderCompiter(const string& shading_language);
    virtual void setPreferredShadingLanguage(const string& name);
    virtual void useShaderProgram(const ShaderProgramPtr& prog);

    virtual void resizeViewport(size_t w, size_t h);
    virtual void getViewport(size_t& w, size_t& h);

    virtual TexturePtr createTexture(const TextureDescription& desc, const void* data, int flags);

    GLenum getEnum(const string& name);

	virtual StateObjectPtr createStateObject(const MiscStateDescription& desc);
    virtual void setStateObject(const StateObjectPtr& state_obj);
    virtual StateObjectPtr getStateObject();

    virtual void setScissorRect(size_t x, size_t y, size_t width, size_t height);

	virtual BlendStateObjectPtr createBlendStateObject(const BlendStateDescription& desc);
    virtual void setBlendStateObject(const BlendStateObjectPtr& bstate_obj);
    virtual BlendStateObjectPtr getBlendStateObject();

	virtual DepthStencilStateObjectPtr createDepthStencilStateObject(const DepthStencilStateDescription& desc);
    virtual void setDepthStencilStateObject(const DepthStencilStateObjectPtr& dsstate_obj);
    virtual DepthStencilStateObjectPtr getDepthStencilStateObject();

    virtual FramebufferPtr createFramebuffer();
    virtual void bindFramebuffer(const FramebufferPtr& fb);
    virtual void bindDrawFramebuffer(const FramebufferPtr& fb);
    virtual void bindReadFramebuffer(const FramebufferPtr& fb);
    virtual void blitFramebuffer(int buffers);
    virtual FramebufferPtr getDrawFramebuffer();
    virtual FramebufferPtr getReadFramebuffer();
    
    virtual Vector4 getClearColor() const;
    virtual void setClearColor(const Vector4& color);
    virtual void clearColorBuffer(size_t idx, const Vector4& color);
    virtual void clearDepthStencilBuffer(float depth=1.0f, int stencil=0);
    virtual void clearAll();

    virtual void registerIncludeLoader(const IncludeLoaderPtr& include_loader) { m_IncludeLoader = include_loader; }
    virtual IncludeLoaderPtr getIncludeLoader() { return m_IncludeLoader; }
    
    virtual Matrix4 createPerspectiveMatrix(float fovy, float aspect, float zNear, float zFar);
    virtual Matrix4 createOrthoMatrix(float left, float right, float bottom, float top, float _near=-1.0f, float _far=1.0f);

    void requires(const char* ext);
};

class GLStateObject: public StateObject
{
    typedef PFNGLENABLEPROC StateType;
    StateType   m_DepthClip,
                m_Multisample,
                m_Scissor,
                m_AALines;
    float       m_DepthBiasFactor,
                m_DepthBiasUnits;
    GLenum      m_CullFace,
                m_FillMode;
public:
	GLStateObject(const MiscStateDescription& desc);
    virtual ~GLStateObject() {}

    void setup();
};

class GLBlendStateObject: public BlendStateObject
{
    bool    m_Enabled;
    GLenum  m_SrcBlend,
            m_DstBlend,
            m_BlendOp,
            m_SrcBlendAlpha,
            m_DstBlendAlpha,
            m_BlendAlphaOp;
public:
	GLBlendStateObject(const BlendStateDescription& desc);
    virtual ~GLBlendStateObject() {}

    void setup();
private:
    GLenum getGLBlendFunc(CEBlend blendf);
    GLenum getGLBlendEquation(CEBlendOperation blendop);
};

class GLDepthStencilStateObject: public DepthStencilStateObject
{
    typedef PFNGLENABLEPROC StateType;
    StateType   m_DepthTest,
                m_StencilTest;
    GLboolean   m_DepthMask;
    GLenum      m_DepthFunc,
                m_SFrontSFail,
                m_SFrontDPFail,
                m_SFrontDPPass,
                m_SFrontFunc,
                m_SBackSFail,
                m_SBackDPFail,
                m_SBackDPPass,
                m_SBackFunc;
    GLuint      m_StencilMask;
    GLint       m_StencilRef;
public:
    GLDepthStencilStateObject(const DepthStencilStateDescription& desc);
    virtual ~GLDepthStencilStateObject() {}

    void setup();
private:
    GLenum getCompFunc(CECompFunc sfunc);
    GLenum getStencilOp(CEStencilOperation sop);
};

class GLTexture: public Texture
{
    friend class GLFramebuffer;

    GLenum m_Target;
    GLuint m_TexId;
public:
    GLTexture(const TextureDescription& desc, const void* data, int flags);
    virtual ~GLTexture();

    void bind() const;
    void bind(GLenum n) const;

    GLint getWidth(GLint level) const;
    GLint getHeight(GLint level) const;
};

typedef shared_ptr<GLTexture> GLTexturePtr;
typedef shared_ptr<const GLTexture> GLTextureConstPtr;

class GLVideoBuffer: public VideoBuffer
{
    GLenum m_Type;
    GLuint m_BufferID;
public:
    GLVideoBuffer(GLenum type, GLsizeiptr size, const GLvoid* data, GLenum usage);
    virtual ~GLVideoBuffer();

    void bind() const;
};

struct GLInputElement
{
    GLuint    location;
    GLsizei   size;
    GLenum    type;
    GLboolean normalized;
    size_t    offset;
};

typedef DynamicArray<GLInputElement> GLInputElementArray;

class GLInputLayout: public InputLayout
{
    GLInputElementArray m_InputElems;
public:
    GLInputLayout(const GLInputElementArray& ielems);
    virtual ~GLInputLayout() {}

    void setup(const ShaderProgramPtr& prog, GLsizei stride, GLsizei offset) const;
};

class GLUniformVar: public UniformVar
{
    friend class GLShaderProgram;
    GLint   m_Location;
    GLuint  m_Program;
public:
    GLUniformVar()
        :   m_Location(-1),
            m_Program(0) {}

    GLUniformVar(GLuint prog, GLint l)
        :   m_Location(l),
            m_Program(prog) { assert(l != -1); }

    GLUniformVar(const GLUniformVar& uv)
        :   m_Location(uv.m_Location),
            m_Program(uv.m_Program)  {}

    virtual ~GLUniformVar() {}

    UniformVar& operator=(const GLUniformVar& uv) { m_Location = uv.m_Location; m_Program = uv.m_Program; return *this; }

    virtual void set(uint32 i) { glUniform1ui(m_Location, i); }
    virtual void set(const uint32* const arr, size_t N) { glUniform1uiv(m_Location, N, arr); CHECK_OPENGL(); }
    virtual void set(int32 i) { glUniform1i(m_Location, i); CHECK_OPENGL(); }
    virtual void set(const int32* const arr, size_t N) { glUniform1iv(m_Location, N, arr); CHECK_OPENGL(); }
    virtual void set(const Quaternion* const arr, size_t N) { glUniform4fv(m_Location, N, arr->elem); CHECK_OPENGL(); }
    virtual void set(const DualQuaternion* const arr, size_t N) { glUniform4fv(m_Location, 2*N, reinterpret_cast<const GLfloat*>(arr)); CHECK_OPENGL(); }
    virtual void set(const Matrix4* const arr, size_t N) { glUniformMatrix4fv(m_Location, N, GL_FALSE, reinterpret_cast<const GLfloat*>(arr)); CHECK_OPENGL(); }
    virtual void set(const Vector4* const arr, size_t N) { glUniform4fv(m_Location, N, arr->elem); CHECK_OPENGL(); }
    virtual void set(const Vector3* const arr, size_t N) { glUniform3fv(m_Location, N, arr->elem); CHECK_OPENGL(); }
    virtual void set(const Vector2* const arr, size_t N) { glUniform2fv(m_Location, N, arr->elem); CHECK_OPENGL(); }
    virtual void set(const float* const arr, size_t N) { glUniform1fv(m_Location, N, arr); CHECK_OPENGL(); }

    virtual void set(const DualQuaternion& dq) { glUniform4fv(m_Location, 2, reinterpret_cast<const GLfloat*>(&dq)); CHECK_OPENGL(); }
    virtual void set(const Quaternion& q) { glUniform4fv(m_Location, 1, q.elem); CHECK_OPENGL(); }
    virtual void set(const Vector4& vec) { glUniform4fv(m_Location, 1, vec.elem); CHECK_OPENGL(); }
    virtual void set(const Vector3& vec) { glUniform3fv(m_Location, 1, vec.elem); CHECK_OPENGL(); }
    virtual void set(const Vector2& vec) { glUniform2fv(m_Location, 1, vec.elem); CHECK_OPENGL(); }
    virtual void set(float f) { glUniform1f(m_Location, f); CHECK_OPENGL(); }
    virtual void set(const Matrix4& mat) { glUniformMatrix4fv(m_Location, 1, GL_FALSE, reinterpret_cast<const GLfloat*>(&mat)); CHECK_OPENGL(); }
    
    virtual void get(uint32& integer) const { glGetUniformuiv(m_Program, m_Location, &integer); }
    virtual void get(uint32* const arr, size_t N) const { for(size_t i = 0; i < N; ++i) glGetUniformuiv(m_Program, m_Location + i, &arr[i]); }
    virtual void get(int32& integer) const { glGetUniformiv(m_Program, m_Location, &integer); }
    virtual void get(int32* const arr, size_t N) const { for(size_t i = 0; i < N; ++i) glGetUniformiv(m_Program, m_Location + i, &arr[i]); }
    virtual void get(Quaternion* const arr, size_t N) const { for(size_t i = 0; i < N; ++i) glGetUniformfv(m_Program, m_Location + i, reinterpret_cast<GLfloat*>(&arr[i])); }
    virtual void get(DualQuaternion* const arr, size_t N) const { for(size_t i = 0; i < N; ++i) glGetUniformfv(m_Program, m_Location + i*2, reinterpret_cast<GLfloat*>(&arr[i*2])), glGetUniformfv(m_Program, m_Location + i*2 + 1, reinterpret_cast<GLfloat*>(&arr[i*2 + 1])); }
    virtual void get(Matrix4* const arr, size_t N) const { for(size_t i = 0; i < N; ++i) glGetUniformfv(m_Program, m_Location + i, reinterpret_cast<GLfloat*>(&arr[i])); }
    virtual void get(Vector4* const arr, size_t N) const { for(size_t i = 0; i < N; ++i) glGetUniformfv(m_Program, m_Location + i, reinterpret_cast<GLfloat*>(&arr[i])); }
    virtual void get(Vector3* const arr, size_t N) const { for(size_t i = 0; i < N; ++i) glGetUniformfv(m_Program, m_Location + i, reinterpret_cast<GLfloat*>(&arr[i])); }
    virtual void get(Vector2* const arr, size_t N) const { for(size_t i = 0; i < N; ++i) glGetUniformfv(m_Program, m_Location + i, reinterpret_cast<GLfloat*>(&arr[i])); }
    virtual void get(float* const arr, size_t N) const { for(size_t i = 0; i < N; ++i) glGetUniformfv(m_Program, m_Location + i, reinterpret_cast<GLfloat*>(&arr[i])); }

    virtual void get(Vector4& vec) const { glGetUniformfv(m_Program, m_Location, reinterpret_cast<GLfloat*>(&vec)); }
    virtual void get(Vector3& vec) const { glGetUniformfv(m_Program, m_Location, reinterpret_cast<GLfloat*>(&vec)); }
    virtual void get(Vector2& vec) const { glGetUniformfv(m_Program, m_Location, reinterpret_cast<GLfloat*>(&vec)); }
    virtual void get(float& f) const { glGetUniformfv(m_Program, m_Location, &f); }
    virtual void get(Matrix4& mat) const { glGetUniformfv(m_Program, m_Location, reinterpret_cast<GLfloat*>(&mat)); }
    virtual void get(DualQuaternion& dq) const { glGetUniformfv(m_Program, m_Location, reinterpret_cast<GLfloat*>(&dq.non_dual)); glGetUniformfv(m_Program, m_Location, reinterpret_cast<GLfloat*>(&dq.dual)); }
    virtual void get(Quaternion& q) const { glGetUniformfv(m_Program, m_Location, reinterpret_cast<GLfloat*>(&q)); }
};

class GLTechnique;

class GLShader: public non_copyable
{
    friend class GLTechnique;

    GLuint m_Id;
public:
    GLShader(GLenum type, const string& source);
    ~GLShader();
};

typedef shared_ptr<GLShader> GLShaderPtr;

class GLSampler
{
    GLuint  m_Sampler;
public:
    GLSampler();
     ~GLSampler();

    void bind(GLuint texunit);
    void setParameter(GLenum state, GLenum val);
};

typedef shared_ptr<GLSampler> GLSamplerPtr;

class GLTextureVar: public TextureVar
{
    GLint           m_TexUnit;
    GLint           m_Location;
    GLSamplerPtr    m_Sampler;
public:
    GLTextureVar(GLint texunit, const GLSamplerPtr& samp);
    virtual ~GLTextureVar();

    virtual void set(const TexturePtr& tex);
    virtual void set(TexturePtr* arr, size_t N);

    void updateLocation(GLint loc) { m_Location = loc; }

    virtual void reset() {} // TODO
};

typedef shared_ptr<GLTextureVar> GLTextureVarPtr;
typedef shared_ptr<const GLTextureVar> GLTextureVarConstPtr;

typedef std::unordered_map<string, GLTextureVarPtr> SamplerMap;

class GLTechnique
{
    typedef std::unordered_map<string, GLuint> AttributeMap;
    SamplerMap      m_SamplerMap;
    AttributeMap    m_AttrMap;
    GLuint          m_Id;
public:
    GLTechnique();
     ~GLTechnique();

    void use() const;
    void mapAttrib(GLuint loc, const string& intl_name, const string& name);
    GLuint getAttribLocation(const string& name) const;

    UniformVarPtr getVariable(const string& name);
    TextureVarPtr getTexture(const string& name);

    void getVariableType(const string& name, UniformVarTypeDesc& desc);
    size_t getSamplerSize(const string& name);
    
    void addTextureVar(const string& name, const GLTextureVarPtr& ptr);

    void attach(GLShader& shader);
    void link();
    void validate();
    void update();
};

typedef shared_ptr<GLTechnique> GLTechniquePtr;

class GLShaderProgram: public ShaderProgram
{
#ifdef CE_RELOADABLE_RESOURCES
    GLRenderer&     m_Renderer;
#endif
    
    typedef std::unordered_map<string, GLTechniquePtr> TechniqueMap;
    TechniqueMap    m_TechniqueMap;

    GLTechniquePtr  m_CurrentTechnique;
public:
    GLShaderProgram(GLRenderer& rend, const string& filename);
    virtual ~GLShaderProgram();

    void use() const;
    GLuint getAttribLocation(const string& name) const;

    virtual void useTechnique(const string& name);

    virtual UniformVarPtr getVariable(const string& name);
    virtual TextureVarPtr getTexture(const string& name);

    virtual void getVariableType(const string& name, UniformVarTypeDesc& desc);

    virtual void apply() const {}

#ifdef CE_RELOADABLE_RESOURCES
    virtual void reload(const string& filename);
#endif
private:
    void compile(GLRenderer& rend, const string& filename);
};

class GLFramebuffer: public Framebuffer
{
    GLuint                      m_FBO;
    DynamicArray<GLenum>        m_BufIds;
    FBAttachmentInfo            m_DepthStencil;
    FBAttachmentArray           m_Textures;
public:
    GLFramebuffer();
    virtual ~GLFramebuffer();

    void bind();
    void bindDraw();
    void bindRead();

    void clearAll();

    virtual void attachDepthStencilTexture(const FBAttachmentInfo& texture);
    virtual void attachTextures(const FBAttachmentArray& textures);

    virtual bool validate();

    FBAttachmentInfo& getColorAttachment(size_t);
    FBAttachmentInfo& getDepthStencil();
};
}

#endif /* GLRENDERER_HH_ */
